/**
 * Objetos da otimização Ant.
 */
package br.org.bertol.mestrado.engine.optimisation.entity;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;

import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.fitness.AbstractCombinatorialFitness;

/**
 * @author contaqualquer
 * @param <T>
 *            Tipo de objeto a ser manipulado
 */
public class Ant<T> extends AbstractUnit<T> {

    /***/
    private final transient float  fAlpha;

    /***/
    private final transient float  fBeta;

    /**
     * Threshold para formiga selecionar caminho via argmax ou roleta.
     */
    private final transient double wellThreshold;

    /***/
    private transient double       pheronomeTrail;

    /***/
    private transient double       trailProb;

    /**
     * @param wThreshold
     *            Limiar de seleção de roleta
     * @param fitness
     *            Tipo de cálculo do problema
     * @param alpha
     *            Parâmetro para atualização do feromonio
     * @param beta
     *            Parâmetro para atualização do feromonio
     * @param objectives
     *            Objetivos do problema
     */
    public Ant(final float alpha, final float beta, final double wThreshold,
        final AbstractCombinatorialFitness<T> fitness,
        final Objective... objectives) {

        super(fitness, objectives);

        this.fAlpha = alpha;

        this.fBeta = beta;

        trailProb = 0d;

        wellThreshold = wThreshold;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void update() {

    }

    /**
     * Reseta a formiga.
     */
    public final void reset() {

        route.clear();

        abstractCombinatorialFitness.reset();

        for (final Objective objective : height.keySet()) {
            height.put(objective, 0);
        }

    }

    /**
     * Método que faz formiga construir o caminho.
     * @param pheromones
     *            Matriz de feromônios
     * @throws CloneNotSupportedException
     *             Erro ao clonar objeto
     */
    @SuppressWarnings("unchecked")
    public final void doPath(final T startClass,
            final ArrayList<T> startClasses,
            final EnumMap<Objective, Double[][]> pheromones,
            final Objective... objectives)
            throws CloneNotSupportedException {

        // parâmetros complementares
        final EnumMap<Objective, Double> auxPar =
                new EnumMap<Objective, Double>(Objective.class);

        for (double j = 0, aux = Math.random(); j < objectives.length; j++, aux = 1 - aux) {

            final Objective objective =
                    objectives[(int) j];

            auxPar.put(objective, aux);
        }

        // remove classe inicial da lista de candidatas
        final ArrayList<T> classes = (ArrayList<T>) startClasses.clone();

        route.add(startClass);

        classes.remove(startClass);

        final List<Ant<T>> candidatos = new ArrayList<Ant<T>>();

        calcFitness();

        final int totalChanges = (int) (startClasses.size() * Math.random());

        // porcentagem de escolha para a roleta
        final double percentage = Math.random();

        // while (!classes.isEmpty()) {
        while (route.size() < startClasses.size() * 0.5) {

            // monta lista de candidatos
            for (final T classe : classes) {

                final Ant<T> clone = (Ant<T>) clone();

                clone.addClass(classe);

                candidatos.add(clone);
                // }
            }

            for (Ant<T> ant : candidatos) {
                ant.calcFitness();
            }

            // posição que obteve a maior contagem de feromônios
            Ant<T> maxPherPath = new Ant<T>(fAlpha, fBeta, wellThreshold,
                    abstractCombinatorialFitness, getHeight()
                    .keySet()
                    .toArray(new Objective[] {}));

            double totalProb = 0d;

            // selecina candidato 1
            // para cada candidato verifica o custo da solução
            for (final Ant<T> ant : candidatos) {

                // calcula a quantidade de feromônios para este caminho

                ant.calcPheromoneTrail(startClass, pheromones, auxPar,
                                       startClasses,
                                       objectives);

                totalProb += ant.pheronomeTrail;

                // guarda caminho com maior taxa de feromônios
                if (ant.pheronomeTrail > maxPherPath.pheronomeTrail) {
                    maxPherPath = (Ant<T>) ant.clone();
                }
            }

            rouletteWheel(candidatos, maxPherPath, totalProb, percentage);

            // remove classe a lista de disponíveis
            classes.removeAll(route);

            // limpa lista de candidatos
            candidatos.clear();

        }
    }

    /**
     * Realiza a atualização da partícula por roleta.
     * @param candidatos
     *            Lista de formigas candidatas
     * @param maxPherPath
     *            Formiga com mais feromônios
     * @param totalProb
     *            Chance total de escolah
     * @param percentage
     *            Porcentagem de escolha do caminho
     * @throws CloneNotSupportedException
     *             Erro ao clonar fitness
     */
    @SuppressWarnings("unchecked")
    private void rouletteWheel(final List<Ant<T>> candidatos,
            final Ant maxPherPath, final double totalProb,
            final double percentage)
            throws CloneNotSupportedException {
        // selecina candidato 2
        // se número rando for menor que o thresold, seleciona argmax
        // caso contrário pega via roleta
        if (percentage > wellThreshold) {

            boolean choose = false;

            for (final Ant ant : candidatos) {

                ant.trailProb = ant.pheronomeTrail / totalProb;

                // caso tenha sido selecionado
                if (ant.trailProb >= (Math.random() / 10)) {
                    // pega caminho
                    // copia o argmax
                    height = ant.height.clone();

                    route = (ArrayList<T>) ant.route.clone();

                    abstractCombinatorialFitness =
                            (AbstractCombinatorialFitness<T>) maxPherPath.abstractCombinatorialFitness
                            .clone();

                    choose = true;

                    break;
                }
            }
            // caso não tenha escolhido nada, pega argmax
            if (!choose) {
                // copia o argmax
                height = maxPherPath.height.clone();

                route = (ArrayList<T>) maxPherPath.route.clone();

                abstractCombinatorialFitness =
                        (AbstractCombinatorialFitness<T>) maxPherPath.abstractCombinatorialFitness
                        .clone();
            }
        } else {
            // copia o argmax
            height = maxPherPath.height.clone();

            route = (ArrayList<T>) maxPherPath.route.clone();

            abstractCombinatorialFitness =
                    (AbstractCombinatorialFitness<T>) maxPherPath.abstractCombinatorialFitness
                    .clone();
        }
    }

    /**
     * Calcula o valor de feromônio para seleção via roleta.
     * @param startClass
     *            Classe de início do caminho
     * @param pheromones
     *            Matrizes de feromnônios
     * @param auxPar
     *            Parâmetros auxiliares
     * @param classes
     *            Classes do sistema
     * @param objectives
     *            Objetivos do problema
     */
    private void calcPheromoneTrail(final T startClass,
            final EnumMap<Objective, Double[][]> pheromones,
            final EnumMap<Objective, Double> auxPar, final List<T> classes,
            final Objective... objectives) {

        double pheromone = 0d;

        // classe adicionada
        final int endPoint = classes.indexOf(route.get(route.size() - 1));

        final int startPoint = classes.indexOf(startClass);

        for (final Objective objective : objectives) {

            final double auxp = auxPar.get(objective).doubleValue();

            final double depositedPheromone = pheromones.get(objective)
                    [startPoint][endPoint];

            Number objVal = height.get(objective);

            objVal = Double.parseDouble(objective.formatValue(objVal));

            pheromone = pheromone
                    + (auxp * (Math.pow(depositedPheromone, fAlpha))
                    * (1 / Math.pow(objVal.doubleValue(), fBeta)));
        }

        // realiza o set da trilha de feromônios
        pheronomeTrail = pheromone;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public final Object clone() throws CloneNotSupportedException {
        final Ant<T> clone = (Ant<T>) super.clone();

        return clone;
    }
}
